package jbitarrays;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;

import jbitarrays.api.CrawlerReader;
import jbitarrays.api.CrawlerWriter;

/**
 * @see JBitArrays#skipWriter(CrawlerWriter, int, int)
 */
@NotThreadSafe final class PeriodicCrawlerWriter implements CrawlerWriter {
	private CrawlerWriter decorated;
	private final int writeLength;
	private final int skipLength;
	private int bitsLeftToWrite;

	public PeriodicCrawlerWriter(@Nonnull CrawlerWriter decorated, @Nonnegative int writeLength, @Nonnegative int skipLength) {
		this(decorated, writeLength, skipLength, writeLength);
	}

	PeriodicCrawlerWriter(@Nonnull CrawlerWriter decorated, @Nonnegative int writeLength, @Nonnegative int skipLength, @Nonnegative int bitsLeftToWrite) {
		if (decorated == null || skipLength < 0 || writeLength <= 0) {
			throw new IllegalArgumentException("decorated != null && skipLength >= 0 && writeLength > 0");
		}
		this.decorated = decorated;
		this.writeLength = writeLength;
		this.skipLength = skipLength;
		this.bitsLeftToWrite = bitsLeftToWrite;
	}

	public void setNext(boolean value) {
		skipIfRequired();
		--bitsLeftToWrite;
		decorated.setNext(value);
	}

	private void skipIfRequired() {
		if (bitsLeftToWrite == 0) {
			int bitsToSkip = skipLength;
			CrawlerReader read = decorated.toReader(); // we don't want to overwrite what we're skipping
			while (bitsToSkip > 0) {
				int toWrite = Math.min(bitsToSkip, Integer.SIZE);
				read.getNextN(toWrite);
				bitsToSkip -= toWrite;
			}
			decorated = read.toWriter();
			bitsLeftToWrite = writeLength;
		}
	}

	public void setNextN(int howMany, int value) {
		Utils.checkHowMany(howMany);
		while (howMany > 0) {
			skipIfRequired();
			int toWrite = Math.min(howMany, bitsLeftToWrite);
			howMany -= toWrite;
			bitsLeftToWrite -= toWrite;
			decorated.setNextN(toWrite, value >>> howMany);
		}
	}

	@Override public CrawlerWriter clone() {
		return new PeriodicCrawlerWriter(decorated.clone(), writeLength, skipLength, bitsLeftToWrite);
	}

	public CrawlerReader toReader() throws UnsupportedOperationException {
		return new PeriodicCrawlerReader(decorated.toReader(), writeLength, skipLength, bitsLeftToWrite);
	}
}
